<template>
<div>
  <div class="canvas canvas-task-viewer" ref="canvas"></div>
  <div class="button-group-right">
    <a-icon type="environment" class="bpmn-button" @click="handleReset" />
    <a-icon type="zoom-in" class="bpmn-button mt-2" @click="handleIn" />
    <a-icon type="zoom-out" class="bpmn-button" @click="handleOut" />
    <a-icon type="download" class="bpmn-button mt-2" @click="handleDownload" />
  </div>
</div>

</template>

<script>
// 这里只需要预览引入Viewer就可以，如果需要编辑则引入"bpmn-js/lib/Modeler",并且还需要引入左侧编辑栏panel等
import BpmnViewer from 'bpmn-js/lib/Viewer'

import MoveCanvasModule from 'diagram-js/lib/navigation/movecanvas'
import moment from 'moment'
export default {
  props: {
    xml: {
      type: String,
      require: true
    },
    nodeCodes: { // 流程已办理节点id
      type: Object,
      default: () => {
        return {
          completed: [], // 审批通过接口
          pending: [], // 待审批节点
          error: [] // 审批未通过节点
        }
      }
    },
    height: {
      type: [Number, String],
      default: 540
    },
    activityList: {
      type: Array,
      default: () => {
        return []
      }
    }
  },
  data() {
    return {
      bpmnViewer: null,
      zoom: 1,
      elementOverlayIds: '',
      overlays: ''
    }
  },
  mounted() {
    this.init()
    // 初始模型的监听器
    this.initModelListeners()
  },
  methods: {
    init() {
      // 初始时清除图层
      this.bpmnViewer && this.bpmnViewer.destroy()
      this.$refs.canvas.innerHTML = ''
      this.zoom = 1 // 放大缩小比例
      // 初始化canvas
      this.bpmnViewer = new BpmnViewer({
        container: this.$refs.canvas,
        height: this.height, // 根据实际情况设置高度，宽度的话设置父元素的宽度就可以，会自适应的
        additionalModules: [
          MoveCanvasModule
        ]
      })
      if (this.xml) {
        const self = this
        const bpmnXmlStr = this.xml // 从接口获取的xml文件
        this.bpmnViewer.importXML(bpmnXmlStr, function(err) {
          if (err) {
            console.error(err)
          } else {
            const canvas = self.bpmnViewer.get('canvas')
            canvas.zoom('fit-viewport', 'auto')
            // self.setNodeColor(canvas)
            self.fillColor()
          }
        })
      }
    },
    // 设置高亮颜色的class 已废弃
    setNodeColor(canvas) {
      if (Object.keys(this.nodeCodes).length > 0) {
        Object.keys(this.nodeCodes).forEach(key => {
          for (let i = 0; i < this.nodeCodes[key].length; i++) {
            canvas.addMarker(this.nodeCodes[key][i], 'node' + key)
          }
        })
      }
    },
    fillColor() {
      const canvas = this.bpmnViewer.get('canvas')
      this.bpmnViewer.getDefinitions().rootElements[0].flowElements.forEach(n => {
        if (!this.getNodeByTask(n.id)) { // 判断该节点是否在已办理列表内
          return
        }
        const css = this.getResultCss(n.id)
        if (n.$type === 'bpmn:UserTask') { // 用户任务
          canvas.addMarker(n.id, css)

          // 如果节点为待办理、不同意，则不修改后续连线颜色
          if (this.nodeCodes.error.includes(n.id) || this.nodeCodes.pending.includes(n.id)) {
            return ''
          }
          n.outgoing?.forEach(nn => { // 遍历后续线段，设置线段颜色
            canvas.addMarker(nn.id, css)
          })
        } else if (n.$type === 'bpmn:ExclusiveGateway') { // 排他网关
          canvas.addMarker(n.id, css)
          n.outgoing?.forEach(nn => { // 遍历后续线段，设置线段颜色
            if (this.getNodeByTask(nn.targetRef.id)) { // 判断该线段来源节点是否在已办理列表内
              canvas.addMarker(nn.id, css)
            }
          })
        } else if (n.$type === 'bpmn:ParallelGateway') { // 并行、相容网关
          canvas.addMarker(n.id, css)
          n.outgoing?.forEach(nn => { // 遍历后续线段，设置线段颜色
            if (this.getNodeByTask(nn.targetRef.id)) { // 判断该线段来源节点是否在已办理列表内
              canvas.addMarker(nn.id, css)
            }
          })
        } else if (n.$type === 'bpmn:StartEvent') { // 开始节点
          n.outgoing.forEach(nn => { // 开始节点判断输入线段
            canvas.addMarker(nn.id, 'highlight')
            canvas.addMarker(n.id, 'highlight')
          })
        } else if (n.$type === 'bpmn:EndEvent') {
          canvas.addMarker(n.id, 'highlight')
          n.outgoing?.forEach(nn => { // 遍历后续线段，设置线段颜色
            if (this.getNodeByTask(nn.targetRef.id)) { // 判断该线段来源节点是否在已办理列表内
              canvas.addMarker(nn.id, css)
            }
          })
        } else {
          canvas.addMarker(n.id, 'highlight')
          n.outgoing?.forEach(nn => { // 遍历后续线段，设置线段颜色
            if (this.getNodeByTask(nn.targetRef.id)) { // 判断该线段来源节点是否在已办理列表内
              canvas.addMarker(nn.id, css)
            }
          })
        }
      })
    },
    getNodeByTask(id) {
      return this.nodeCodes.completed.includes(id) || this.nodeCodes.pending.includes(id) || this.nodeCodes.error.includes(id)
    },
    getResultCss(id) {
      const completeTask = this.nodeCodes.completed.includes(id)
      const todoTask = this.nodeCodes.pending.includes(id)
      const errorTask = this.nodeCodes.error.includes(id)
      if (todoTask) { // 审批中
        return 'highlight-todo'
      } else if (completeTask) { // 已通过
        return 'highlight'
      } else if (errorTask) {
        return 'highlight-reject'
      }
      // else if (result === 3) { // 不通过
      //   return 'highlight-reject'
      // } else if (result === 4) { // 已取消
      //   return 'highlight-cancel'
      // } else if (result === 5) { // 已退回
      //   return 'highlight-back'
      // } else if (result === 6) { // 已委派
      //   return 'highlight-todo'
      // }
      return ''
    },
    handleIn() {
      this.zoom += 0.1
      var canvas = this.bpmnViewer.get('canvas')
      console.log(canvas)
      canvas.zoom(this.zoom)
    },
    handleOut() {
      this.zoom -= 0.1
      var canvas = this.bpmnViewer.get('canvas')
      canvas.zoom(this.zoom)
    },
    handleReset() {
      this.zoom = 1
      this.bpmnViewer.get('canvas').zoom('fit-viewport', 'auto')
    },
    async handleDownload() {
      const { err, svg } = await this.bpmnViewer.saveSVG()
      // 读取异常时抛出异常
      if (err) {
        return console.error(err)
      }
      const { href, filename } = this.setEncoded('svg', '流程图', svg)
      this.downloadFunc(href, filename)
    },
    // 文件下载方法
    downloadFunc(href, filename) {
      if (href && filename) {
        const a = document.createElement('a')
        a.download = filename // 指定下载的文件名
        a.href = href //  URL对象
        a.click() // 模拟点击
        URL.revokeObjectURL(a.href) // 释放URL 对象
      }
    },
    // 根据所需类型进行转码并返回下载地址
    setEncoded(type, filename = 'diagram', data) {
      const encodedData = encodeURIComponent(data)
      return {
        filename: `${filename}.${type}`,
        href: `data:application/${type === 'svg' ? 'text/xml' : 'bpmn20-xml'};charset=UTF-8,${encodedData}`,
        data: data
      }
    },
    initModelListeners() {
      const EventBus = this.bpmnViewer.get('eventBus')
      const that = this
      // 注册需要的监听事件
      EventBus.on('element.hover', function(eventObj) {
        const element = eventObj ? eventObj.element : null
        that.elementHover(element)
      })
      EventBus.on('element.out', function(eventObj) {
        const element = eventObj ? eventObj.element : null
        that.elementOut(element)
      })
    },
    // 流程图的元素被 hover
    elementHover(element) {
      this.element = element
      !this.elementOverlayIds && (this.elementOverlayIds = {})
      !this.overlays && (this.overlays = this.bpmnViewer.get('overlays'))
      // 展示信息
      const activity = this.activityList.find(m => (m.taskDefinitionKey === element.id || m.activityId === element.id))
      if (!activity) {
        return
      }
      if (!this.elementOverlayIds[element.id] && element.type !== 'bpmn:Process') {
        if (!['bpmn:StartEvent', 'bpmn:EndEvent', 'bpmn:ServiceTask', 'bpmn:UserTask'].includes(element.type)) {
          return false
        }
        let html = `<div class="element-overlays">无信息</div>` // 默认值
        if (element.type === 'bpmn:StartEvent') {
          html = `<p>发起时间：${moment(activity.endTime).format('YYYY-MM-DD HH:mm:ss')}`
        } else if (element.type === 'bpmn:UserTask') {
          html = `<p>办理人：${activity.assignee ? activity.textMap?.assignee$DICT_TEXT_ : '暂无'}</p>
                  <p>开始时间：${moment(activity.createTime || activity.startTime).format('YYYY-MM-DD HH:mm:ss')}`
          if (activity.endTime) {
            html += `<p>结束时间：${moment(activity.endTime).format('YYYY-MM-DD HH:mm:ss')}</p>`
          }
          if (activity.canceled === false || activity.canceled === true) {
            html += `<p>审批意见：${activity.canceled ? `${activity.comment || '不通过'}` : '通过'}</p>`
          }
        } else if (element.type === 'bpmn:ServiceTask') {
          if (activity.createTime) {
            html = `<p>创建时间：${moment(activity.createTime || activity.startTime).format('YYYY-MM-DD HH:mm:ss')}</p>`
          }
          if (activity.endTime) {
            html += `<p>结束时间：${moment(activity.endTime).format('YYYY-MM-DD HH:mm:ss')}</p>`
          }
        } else if (element.type === 'bpmn:EndEvent') {
          html = `<p>结果：通过</p>`
          if (activity.endTime) {
            html += `<p>结束时间：${moment(activity.endTime).format('YYYY-MM-DD HH:mm:ss')}</p>`
          }
        }
        this.elementOverlayIds[element.id] = this.overlays.add(element, {
          position: { left: 0, bottom: 0 },
          html: `<div class="element-overlays">${html}</div>`
        })
      }
    },
    // 流程图的元素被 out
    elementOut(element) {
      this.overlays.remove({ element })
      this.elementOverlayIds[element.id] = null
    }
  }
}
</script>
<style lang="less">
.canvas-task-viewer{
  .bjs-breadcrumbs{
    display: none;
  }
}

.button-group-right{
  flex-direction: column;
  align-items: center;
  position: absolute;
  bottom: 35%;
  right: 15px;
  z-index: 1000;
  height: 23px;
  display: flex;
}
.bpmn-button{
  color: #4e5969;
  background-color: #f2f3f5;
  border: 1px solid transparent;
  font-size: 20px;
  padding: 3px;
}

// 已办理
.highlight.djs-shape .djs-visual > :nth-child(1) {
  fill: rgb(18, 202, 110) !important;
  stroke: rgb(18, 202, 110) !important;
  fill-opacity: 0.2 !important;
}
.highlight.djs-shape .djs-visual > :nth-child(2) {
  fill: rgb(18, 202, 110) !important;
  stroke:rgb(18, 202, 110) !important;
  fill-opacity: 0.2 !important;
}
.highlight.djs-shape .djs-visual > path {
  fill: rgb(18, 202, 110) !important;
  fill-opacity: 0.2 !important;
  stroke: rgb(18, 202, 110) !important;
}
.highlight.djs-connection > .djs-visual > path {
  stroke: rgb(18, 202, 110) !important;
}

// 待办理
.highlight-todo.djs-connection > .djs-visual > path {
  stroke: orange !important;
  stroke-dasharray: 4px !important;
  fill-opacity: 0.2 !important;
}
.highlight-todo.djs-shape .djs-visual > :nth-child(1) {
  fill: orange !important;
  stroke: orange !important;
  stroke-dasharray: 4px !important;
  fill-opacity: 0.2 !important;
}
.highlight-todo.djs-shape .djs-visual > :nth-child(2) {
  fill: orange !important;
  stroke:orange !important;
  fill-opacity: 0.2 !important;
}
.highlight-todo.djs-shape .djs-visual > path {
  fill: orange !important;
  fill-opacity: 0.2 !important;
  stroke: orange !important;
}
.highlight-todo.djs-shape .djs-visual>*:not(.djs-label):not(path) {
  animation: waiting 35s linear infinite !important;
}
.highlight-todo.djs-connection>.djs-visual>path {
  animation: waiting 35s linear infinite !important;
}

// 不同意
.highlight-reject.djs-shape .djs-visual > :nth-child(1) {
  fill: red !important;
  stroke: red !important;
  fill-opacity: 0.2 !important;
}
.highlight-reject.djs-shape .djs-visual > :nth-child(2) {
  fill: red !important;
}
.highlight-reject.djs-shape .djs-visual > path {
  fill: red !important;
  fill-opacity: 0.2 !important;
  stroke: red !important;
}
.highlight-reject.djs-connection > .djs-visual > path {
  stroke: red !important;
}

.highlight-reject:not(.djs-connection) .djs-visual > :nth-child(1) {
  fill: red !important; /* color elements as green */
}

:deep(.highlight-reject.djs-shape .djs-visual > :nth-child(1)) {
  fill: red !important;
  stroke: red !important;
  fill-opacity: 0.2 !important;
}
:deep(.highlight-reject.djs-shape .djs-visual > :nth-child(2)) {
  fill: red !important;
}
:deep(.highlight-reject.djs-shape .djs-visual > path) {
  fill: red !important;
  fill-opacity: 0.2 !important;
  stroke: red !important;
}
:deep(.highlight-reject.djs-connection > .djs-visual > path) {
  stroke: red !important;
}

@keyframes waiting {
  0%{
    stroke-dashoffset: 1000;
  }
}
.element-overlays {
  box-sizing: border-box;
  padding: 8px;
  background: rgba(0, 0, 0, 0.8);
  border-radius: 4px;
  color: #fafafa;
  width: 260px;
}
</style>
